package com.uduemc.biso.node.web.interceptor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.WordTree;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.uduemc.biso.core.extities.center.Host;
import com.uduemc.biso.core.extities.center.Site;
import com.uduemc.biso.core.extities.center.SysLanguage;
import com.uduemc.biso.core.extities.center.SysServer;
import com.uduemc.biso.node.core.common.entities.HostInfos;
import com.uduemc.biso.node.core.entities.HConfig;
import com.uduemc.biso.node.core.entities.HDomain;
import com.uduemc.biso.node.core.entities.HRedirectUrl;
import com.uduemc.biso.node.core.entities.SCodeSite;
import com.uduemc.biso.node.core.entities.custom.HConfigIp4Release;
import com.uduemc.biso.node.core.utils.SiteUrlUtil;
import com.uduemc.biso.node.web.component.RequestHolder;
import com.uduemc.biso.node.web.component.SiteHolder;
import com.uduemc.biso.node.web.component.SpringContextUtils;
import com.uduemc.biso.node.web.component.wordfilter.CommonFilter;
import com.uduemc.biso.node.web.config.SpringContextUtil;
import com.uduemc.biso.node.web.entities.AccessAgent;
import com.uduemc.biso.node.web.entities.AccessHost;
import com.uduemc.biso.node.web.exception.*;
import com.uduemc.biso.node.web.exception.master.HostEndException;
import com.uduemc.biso.node.web.exception.master.HostReviewException;
import com.uduemc.biso.node.web.exception.master.HostStatusException;
import com.uduemc.biso.node.web.exception.master.HostTrialException;
import com.uduemc.biso.node.web.service.Ip2regionService;
import com.uduemc.biso.node.web.service.WordFilterService;
import com.uduemc.biso.node.web.service.byfeign.*;
import com.uduemc.biso.node.web.utils.DeviceUtils;
import com.uduemc.biso.node.web.utils.IpUtil;
import com.uduemc.biso.node.web.utils.SchemeUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 针对域名的拦截器 通过拦截获取域名对应的host、site数据信息，以及是否能够有权限访问的过滤
 *
 * @author guanyi
 */
@Component
public class HostInterceptor implements HandlerInterceptor {

    @Resource
    private RequestHolder requestHolder;

    @Resource
    private SpringContextUtil springContextUtil;

    @Resource
    private CommonFilter commonFilter;

    @Resource
    private SiteHolder siteHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 加入 holder
        requestHolder.set(request);

        AccessHost accessHost = requestHolder.getAccessHost();
        AccessAgent accessAgent = requestHolder.getAccessAgent();
        SysServer sysServer = requestHolder.getSysServer();

        // 访问的IP地址
        String ipAddr = IpUtil.getIpAddr(request);
        String domain = accessHost.getDomain();
        HDomain hDomain = accessHost.getHDomain();
        String uri = request.getRequestURI();

        DomainService domainServiceImpl = SpringContextUtils.getBean("domainServiceImpl", DomainService.class);
        RedirectUrlService redirectUrlServiceImpl = SpringContextUtils.getBean("redirectUrlServiceImpl", RedirectUrlService.class);

        // 是否是代理商次控段后台域名访问站点端数据
        boolean agentAccessDomainName = accessAgent.isAgentDomain() && accessAgent.getAgentNodeServerDomain() != null
                && accessAgent.getAgentNodeServerDomain().getDomainName().equals(domain);
        if (sysServer != null && sysServer.getDomain().equals(domain) || (springContextUtil.local() && "localhost".equals(domain)) || agentAccessDomainName) {
            // 判断是否是系统域名，如果是系统域名，判断后缀是否是 /site/\w+/ 开头，如果都不是，则直接404抛出异常！
            if (StrUtil.startWith(uri, "/site/")) {
                // 获取到 /site/(\w)+/ code 的数据
                String pattern = "(/site/([\\w-]+))(.*)";
                // 创建 Pattern 对象
                Pattern r = Pattern.compile(pattern);
                // 现在创建 matcher 对象
                Matcher m = r.matcher(uri);
                if (!m.find()) {
                    throw new NotFoundDomainException(request.getRequestURL() + " 通过正则解析uri失败！");
                }
                String uriprefix = m.group(1);
                String token = m.group(2);
                if (StrUtil.isBlank(uriprefix) || StrUtil.isBlank(token)) {
                    throw new NotFoundDomainException(request.getRequestURL() + " 通过正则解析uri失败！");
                }
                String code = SiteUrlUtil.validCode(token);
                if (StrUtil.isBlank(code)) {
                    throw new NotFoundDomainException(request.getRequestURL() + " 通过解密获取 token 中的 code 失败！");
                }
                String newuri = StrUtil.sub(uri, uriprefix.length(), uri.length());
                if (StrUtil.isBlank(newuri)) {
                    throw new NotFoundDomainException(request.getRequestURL() + " 截取后新的 uri 为空，解析uri失败！");
                }

                accessHost = existUri(code, newuri, uriprefix, request);

                // 链接 重定向
                HRedirectUrl hRedirectUrl = redirectUrlServiceImpl.findOkOneByHostIdFromUrl(accessHost.getHost().getId(), newuri);
                if (hRedirectUrl != null) {
                    response.sendRedirect(uriprefix + hRedirectUrl.getToUrl());
                    return false;
                }

                if (!ip4Access(ipAddr, accessHost.getHostConfig())) {
                    throw new NotAllowIp4AccessException(ipAddr + "," + accessHost.getHost().getId());
                }

                // 加入 holder
                requestHolder.set(accessHost);

                // 写入过滤的关键字
                WordFilterService wordFilterServiceImpl = SpringContextUtils.getBean("wordFilterServiceImpl", WordFilterService.class);
                WordTree filterWordTree = wordFilterServiceImpl.filterWordTree(accessHost.getHostConfig());
                requestHolder.setFilterWordTree(filterWordTree);

                commonFilter.hDomain(accessHost.getHDomain());

                // 写入整站 host 的代码部分
                CodeService codeServiceImpl = SpringContextUtils.getBean("codeServiceImpl", CodeService.class);
                SCodeSite sCodeSite = codeServiceImpl.getCodeSiteByHostSiteId(accessHost.getHost().getId(), 0L);
                siteHolder.setCodeHost(sCodeSite);

                return true;
            }

            // 抛出没有找到域名的异常
            throw new NotFoundDomainException(request.getRequestURL() + " 系统中不存在该链接的站点数据！");
        }
        // 通过 domain 获取域名数据
        if (hDomain == null) {
            // 抛出没有找到域名的异常
            throw new NotFoundDomainException(domain + " 系统中不存在该域名！");
        } else {
            accessHost = existDomain(hDomain, request);

            // 域名 重定向
            Long redirect = hDomain.getRedirect();
            if (redirect != null && redirect.longValue() > 0) {
                HDomain redirectDomain = domainServiceImpl.getInfoByHostDomainId(hDomain.getHostId(), redirect);
                if (redirectDomain != null) {
                    String domainName = redirectDomain.getDomainName();
                    String scheme = SchemeUtil.getScheme(request);
                    UrlBuilder urlBuilder = UrlBuilder.of().setScheme(scheme).setHost(domainName);

                    String[] split = uri.split("/");
                    if (ArrayUtil.isNotEmpty(split)) {
                        for (String str : split) {
                            if (StrUtil.isNotBlank(str)) {
                                urlBuilder.addPath(str);
                            }
                        }
                    }
                    Enumeration<String> parameterNames = request.getParameterNames();
                    while (parameterNames.hasMoreElements()) {
                        String nextElement = parameterNames.nextElement();
                        String parameter = request.getParameter(nextElement);
                        urlBuilder.addQuery(nextElement, parameter);
                    }
                    response.sendRedirect(urlBuilder.build());
                    return false;
                }
            }

            // 判断是否是ssl，是否强制 https 访问
            boolean sslHttps = domainServiceImpl.sslHttps(hDomain.getHostId(), hDomain.getId());
            if (sslHttps && accessHost.isSsl() == false) {
                UrlBuilder urlBuilder = UrlBuilder.of().setScheme("https").setHost(request.getServerName());
                String[] split = uri.split("/");
                if (ArrayUtil.isNotEmpty(split)) {
                    for (String str : split) {
                        if (StrUtil.isNotBlank(str)) {
                            urlBuilder.addPath(str);
                        }
                    }
                }
                Enumeration<String> parameterNames = request.getParameterNames();
                while (parameterNames.hasMoreElements()) {
                    String nextElement = parameterNames.nextElement();
                    String parameter = request.getParameter(nextElement);
                    urlBuilder.addQuery(nextElement, parameter);
                }
                response.sendRedirect(urlBuilder.build());
                return false;
            }

            // 链接 重定向
            HRedirectUrl hRedirectUrl = redirectUrlServiceImpl.findOkOneByHostIdFromUrl(accessHost.getHost().getId(), uri);
            if (hRedirectUrl != null) {
                response.sendRedirect(hRedirectUrl.getToUrl());
                return false;
            }

            if (!ip4Access(ipAddr, accessHost.getHostConfig())) {
                throw new NotAllowIp4AccessException(ipAddr + "," + accessHost.getHost().getId());
            }

            // 加入 holder
            requestHolder.set(accessHost);

            // 写入过滤的关键字
            WordFilterService wordFilterServiceImpl = SpringContextUtils.getBean("wordFilterServiceImpl", WordFilterService.class);
            WordTree filterWordTree = wordFilterServiceImpl.filterWordTree(accessHost.getHostConfig());
            requestHolder.setFilterWordTree(filterWordTree);

            commonFilter.hDomain(accessHost.getHDomain());

            // 写入整站 host 的代码部分
            CodeService codeServiceImpl = SpringContextUtils.getBean("codeServiceImpl", CodeService.class);
            SCodeSite sCodeSite = codeServiceImpl.getCodeSiteByHostSiteId(accessHost.getHost().getId(), 0L);
            siteHolder.setCodeHost(sCodeSite);

            return true;
        }
    }

    private boolean ip4Access(String ipAddr, HConfig hConfig) throws JsonParseException, JsonMappingException, IOException {

        HConfigIp4Release ip4Release = HConfigIp4Release.dataObject(hConfig);
        Ip2regionService ip2regionServiceImpl = SpringContextUtils.getBean("ip2regionServiceImpl", Ip2regionService.class);

        short status = ip4Release.getStatus();
        if (status == (short) 0) {
            return true;
        }

        if (status == (short) 1) {
            // 黑名单
            short blacklistStatus = ip4Release.getBlacklistStatus();
            short blackareaStatus = ip4Release.getBlackareaStatus();
            if (blacklistStatus == (short) 1) {
                List<String> blacklist = ip4Release.getBlacklist();
                if (CollUtil.isNotEmpty(blacklist) && CollUtil.contains(blacklist, ipAddr)) {
                    return false;
                }
            }
            if (blackareaStatus == (short) 1) {
                String ip4Country = ip2regionServiceImpl.ip4Country(ipAddr);
                List<String> blackarea = ip4Release.getBlackarea();
                if (CollUtil.isNotEmpty(blackarea) && CollUtil.contains(blackarea, ip4Country)) {
                    return false;
                }
            }
            return true;
        } else if (status == (short) 2) {
            // 白名单
            short whitelistStatus = ip4Release.getWhitelistStatus();
            short whiteareaStatus = ip4Release.getWhiteareaStatus();
            if (whitelistStatus == (short) 1) {
                List<String> whitelist = ip4Release.getWhitelist();
                if (CollUtil.isNotEmpty(whitelist) && CollUtil.contains(whitelist, ipAddr)) {
                    return true;
                }
            }
            if (whiteareaStatus == (short) 1) {
                String ip4Country = ip2regionServiceImpl.ip4Country(ipAddr);
                List<String> whitearea = ip4Release.getWhitearea();
                if (CollUtil.isNotEmpty(whitearea) && CollUtil.contains(whitearea, ip4Country)) {
                    return true;
                }
            }

            return false;
        }
        return true;
    }

    private AccessHost existUri(String code, String newuri, String uriprefix, HttpServletRequest request) throws JsonParseException, JsonMappingException,
            JsonProcessingException, IOException, NotInfoHostException, NotInitHostException, HostStatusException {
        HostService hostServiceImpl = SpringContextUtils.getBean("hostServiceImpl", HostService.class);
        HostInfos hostInfos = hostServiceImpl.getHostInfosByRandomCode(code);
        if (hostInfos == null || hostInfos.getHost() == null) {
            // 未能获取主机信息
            throw new NotInfoHostException(uriprefix + " 主机数据未能找到！");
        }

        if (hostInfos.getHostConfig() == null) {
            // 站点未能初始化
            throw new NotInitHostException(uriprefix + " 主机数据未能初始化！");
        }

        Host host = hostInfos.getHost();
        Short hostStatus = host.getStatus();
        if (hostStatus == 2) {
            throw new HostStatusException("当前的站点被关闭，请联系管理员！");
        } else if (hostStatus == 4) {
            throw new HostStatusException("当前的站点已被删除！");
        } else if (hostStatus == 1 || hostStatus == 3) {

        } else {
            throw new HostStatusException("当前的站点状态为 " + hostStatus + " ！");
        }

        // 是否低移动设备访问
        boolean wap = DeviceUtils.isMobileDevice(request);
        boolean ios = false;
        boolean weChat = false;
        if (wap) {
            ios = DeviceUtils.isIOSDevice(request);
            weChat = DeviceUtils.isWeChat(request);
        }

        SiteService siteServiceImpl = SpringContextUtils.getBean("siteServiceImpl", SiteService.class);
        LanguageService languageServiceImpl = SpringContextUtils.getBean("languageServiceImpl", LanguageService.class);

        // 获取可用的 site List 列表
        List<Site> listSite = siteServiceImpl.getOkSiteList(host.getId());
        // 所有的语言
        List<SysLanguage> allSysLanguage = languageServiceImpl.getAll();

        String scheme = request.getScheme();
        boolean ssl = scheme.toLowerCase().equals("https");

        String domain = request.getServerName();
        AccessHost accessHost = requestHolder.getAccessHost();
        accessHost.setSsl(ssl);
        accessHost.setPort(request.getServerPort());
        accessHost.setDomain(domain);
        accessHost.setHDomain(null);
        accessHost.setUri(newuri);
        accessHost.setUriprefix(uriprefix);
        accessHost.setWap(wap);
        accessHost.setIos(ios);
        accessHost.setWeChat(weChat);
        accessHost.setHost(hostInfos.getHost());
        accessHost.setHostConfig(hostInfos.getHostConfig());
        accessHost.setListSite(listSite);
        accessHost.setListLanguage(allSysLanguage);

        return accessHost;
    }

    /**
     * 存在 HDomain 数据的时候
     *
     * @param hDomain
     * @return
     * @throws NotRecordDomainException
     * @throws NotAccessDomainException
     * @throws NotInfoHostException
     * @throws IOException
     * @throws JsonProcessingException
     * @throws JsonMappingException
     * @throws JsonParseException
     * @throws NotInitHostException
     * @throws HostStatusException
     * @throws HostReviewException
     * @throws HostTrialException
     * @throws HostEndException
     */
    private AccessHost existDomain(HDomain hDomain, HttpServletRequest request)
            throws NotRecordDomainException, NotAccessDomainException, NotInfoHostException, JsonParseException, JsonMappingException, JsonProcessingException,
            IOException, NotInitHostException, HostStatusException, HostReviewException, HostTrialException, HostEndException {
        if (hDomain == null) {
            return null;
        }
        String domain = hDomain.getDomainName();
        String uri = request.getRequestURI();
        String scheme = SchemeUtil.getScheme(request);
        boolean ssl = scheme.toLowerCase().equals("https");

        if (hDomain.getStatus() == null || hDomain.getStatus().shortValue() != (short) 1) {
            // 未备案
            throw new NotRecordDomainException(
                    domain + " 域名未备案！" + (hDomain.getStatus() != null && hDomain.getStatus().shortValue() == (short) 0 ? "（未校验）" : ""));
        }

        // 是否对外显示
        Short domainType = hDomain.getDomainType();
        if (domainType != null && domainType.shortValue() == (short) 1) {
            if (hDomain.getAccessType() == null || hDomain.getAccessType().shortValue() != (short) 1) {
                throw new NotAccessDomainException(domain + " 域名对外不予以显示！");
            }
        }

        // hostId
        long hostId = hDomain.getHostId();

        HostService hostServiceImpl = SpringContextUtils.getBean("hostServiceImpl", HostService.class);
        // 获取主机信息
        HostInfos hostInfos = hostServiceImpl.getHostInfosByHostId(hostId);
        if (hostInfos == null || hostInfos.getHost() == null) {
            // 未能获取主机信息
            throw new NotInfoHostException(domain + " 主机数据未能找到！");
        }

        if (hostInfos.getHostConfig() == null) {
            // 站点未能初始化
            throw new NotInitHostException(domain + " 主机数据未能初始化！");
        }

        Host host = hostInfos.getHost();
        Short hostStatus = host.getStatus();
        if (hostStatus == 2) {
            throw new HostStatusException("当前的站点被关闭，请联系管理员！");
        } else if (hostStatus == 4) {
            throw new HostStatusException("当前的站点已被删除！");
        } else if (hostStatus == 1 || hostStatus == 3) {

        } else {
            throw new HostStatusException("当前的站点状态为 " + hostStatus + " ！");
        }

        // 绑定域名，验证是否制作审核通过
        if (hDomain.getDomainType() != null && hDomain.getDomainType().shortValue() == (short) 1
                && (host.getReview() == null || host.getReview().shortValue() != 1)) {
            // 制作审核未能通过
            throw new HostReviewException("制作审核未能通过");
        }

        // 绑定域名，如果是试用用户无法正常访问
        if (hDomain.getDomainType() != null && hDomain.getDomainType().shortValue() == (short) 1
                && (host.getTrial() == null || host.getTrial().shortValue() != 1)) {
            // 试用用户无法正常访问
            throw new HostTrialException("试用用户的站点，绑定的域名无法正常访问");
        }

        // 绑定域名，时间到期后不能访问
        if (hDomain.getDomainType() != null && hDomain.getDomainType().shortValue() == (short) 1
                && (host.getEndAt().getTime() / 1000) < LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))) {
            throw new HostEndException(host.getEndAt(), "到期后不能访问");
        }

        // 是否低移动设备访问
        boolean wap = DeviceUtils.isMobileDevice(request);
        boolean ios = false;
        boolean weChat = false;
        if (wap) {
            ios = DeviceUtils.isIOSDevice(request);
            weChat = DeviceUtils.isWeChat(request);
        }

        SiteService siteServiceImpl = SpringContextUtils.getBean("siteServiceImpl", SiteService.class);
        LanguageService languageServiceImpl = SpringContextUtils.getBean("languageServiceImpl", LanguageService.class);

        // 获取可用的 site List 列表
        List<Site> listSite = siteServiceImpl.getOkSiteList(hostId);
        // 所有的语言
        List<SysLanguage> allSysLanguage = languageServiceImpl.getAll();

        AccessHost accessHost = requestHolder.getAccessHost();
        accessHost.setSsl(ssl);
        accessHost.setPort(request.getServerPort());
        accessHost.setDomain(domain);
        accessHost.setHDomain(hDomain);
        accessHost.setUri(uri);
        accessHost.setWap(wap);
        accessHost.setIos(ios);
        accessHost.setWeChat(weChat);
        accessHost.setHost(hostInfos.getHost());
        accessHost.setHostConfig(hostInfos.getHostConfig());
        accessHost.setListSite(listSite);
        accessHost.setListLanguage(allSysLanguage);

        return accessHost;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        siteHolder.clean();
    }
}
